const CodeWriter = @This();
const std = @import("std");
const Command = @import("Command.zig");
const String = []const u8;

writer: std.fs.File.Writer,

const @"A=SP-1" =
    \\@SP
    \\A=M-1
;

const @"*SP=D, SP++" =
    \\@SP
    \\A=M
    \\M=D
    \\@SP
    \\M=M+1
;

const @"SP--, D=*SP" =
    \\@SP
    \\AM=M-1
    \\D=M
;

const @"SP--, D=*SP, A=SP-1" =
    \\@SP
    \\AM=M-1
    \\D=M
    \\A=A-1
;

const map = struct {
    const binary: std.StaticStringMap(String) = .initComptime(.{
        .{ "add", "D+M" },
        .{ "and", "D&M" },
        .{ "sub", "M-D" },
        .{ "or", "D|M" },
    });

    const unary: std.StaticStringMap(u8) = .initComptime(.{
        .{ "neg", '-' },
        .{ "not", '!' },
    });

    const segments: std.StaticStringMap(String) = .initComptime(.{
        .{ "local", "LCL" },
        .{ "argument", "ARG" },
        .{ "this", "THIS" },
        .{ "that", "THAT" },
    });
};

pub fn arithmetic(cw: CodeWriter, op: String) !void {
    const S = struct {
        var index: usize = 0; // static local variable
    };

    switch (op[0]) {
        'e', 'g', 'l' => { // eq, gt, lt
            var output: [2]u8 = undefined;
            try cw.writer.print(
                \\{0s}
                \\D=M-D
                \\@TRUE{1d}
                \\D;J{2s}
                \\{3s}
                \\M=0
                \\@CONTINUE{1d}
                \\0;JMP
                \\(TRUE{1d})
                \\{3s}
                \\M=-1
                \\(CONTINUE{1d})
                \\
            , .{
                @"SP--, D=*SP, A=SP-1",
                S.index,
                std.ascii.upperString(&output, op),
                @"A=SP-1",
            });
            S.index += 1;
        },
        'a', 's', 'o' => { // add, and, sub, or
            try cw.writer.print(
                \\{s}
                \\M={s}
                \\
            , .{
                @"SP--, D=*SP, A=SP-1",
                map.binary.get(op).?,
            });
        },
        'n' => { // neg, not
            try cw.writer.print(
                \\{s}
                \\M={c}M
                \\
            , .{
                @"A=SP-1",
                map.unary.get(op).?,
            });
        },
        else => unreachable,
    }
}

pub fn pushpop(cw: CodeWriter, cmd: Command, basename: String) !void {
    const segment = cmd.arg1;
    const index = cmd.arg2.?;

    switch (segment[0]) {
        'c' => { // constant
            try cw.writer.print(
                \\@{d}
                \\D=A
                \\{s}
                \\
            , .{
                index,
                @"*SP=D, SP++",
            });
        },
        's' => { // static
            if (cmd.type == .push) {
                try cw.writer.print(
                    \\@{s}.{d}
                    \\D=M
                    \\{s}
                    \\
                , .{
                    basename,
                    index,
                    @"*SP=D, SP++",
                });
            } else {
                try cw.writer.print(
                    \\{s}
                    \\@{s}.{d}
                    \\M=D
                    \\
                , .{
                    @"SP--, D=*SP",
                    basename,
                    index,
                });
            }
        },
        'p' => { // pointer
            const address = if (index == 0) "THIS" else "THAT";
            if (cmd.type == .push) {
                try cw.writer.print(
                    \\@{s}
                    \\D=M
                    \\{s}
                    \\
                , .{
                    address,
                    @"*SP=D, SP++",
                });
            } else {
                try cw.writer.print(
                    \\{s}
                    \\@{s}
                    \\M=D
                    \\
                , .{
                    @"SP--, D=*SP",
                    address,
                });
            }
        },
        't', 'l', 'a' => {
            if (std.mem.eql(u8, segment, "temp")) {
                if (cmd.type == .push) {
                    try cw.writer.print(
                        \\@{d}
                        \\D=M
                        \\{s}
                        \\
                    , .{
                        5 + index,
                        @"*SP=D, SP++",
                    });
                } else {
                    try cw.writer.print(
                        \\{s}
                        \\@{d}
                        \\M=D
                        \\
                    , .{
                        @"SP--, D=*SP",
                        5 + index,
                    });
                }
            } else { // local, argument, this, that
                if (cmd.type == .push) {
                    try cw.writer.print(
                        \\@{s}
                        \\D=M
                        \\@{d}
                        \\A=D+A
                        \\D=M
                        \\{s}
                        \\
                    , .{
                        map.segments.get(segment).?,
                        index,
                        @"*SP=D, SP++",
                    });
                } else {
                    try cw.writer.print(
                        \\@{s}
                        \\D=M
                        \\@{d}
                        \\D=D+A
                        \\@R13
                        \\M=D
                        \\{s}
                        \\@R13
                        \\A=M
                        \\M=D
                        \\
                    , .{
                        map.segments.get(segment).?,
                        index,
                        @"SP--, D=*SP",
                    });
                }
            }
        },
        else => unreachable,
    }
}
